-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix make_simplified_union interaction with Any #2197
Conversation
make_simplified_union had two incorrect interactions with Any that this fixes: 1) Any unions containing Any were simplified to Any, which is incorrect. 2) Any classes that inherited from Any would be removed from the union by the subclass simplification (because subclasses of Any are considered subclasses of all other classes due to subtype/compatible with confusion). Note that `Union`s call make_union and not make_simplified_union, so this doesn't change their behavior directly (unless they interact with something else which causes them to be simplified, like being part of an `Optional`).
if i != j and is_subtype(tj, ti): | ||
tj_is_anylike = (isinstance(tj, AnyType) or | ||
(isinstance(tj, Instance) and tj.type.fallback_to_any)) | ||
if i != j and is_subtype(tj, ti) and not tj_is_anylike: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check tj_is_anylike before is_subtype()? It seems perverse to call the latter when you know you're going to reject the result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also my comment #2031 (comment) for another potential improvement (though it wouldn't be consistent with preserving Any
types in unions, but it would hopefully fix order-dependence).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guido: I agree -- will swap the ordering.
Jukka: I think it'd be better to fix is_subtype
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I've changed my mind. I don't feel strongly about this, but I think the conditional is moderately more readable if tj_is_anylike
comes after the subtype check. tj_is_anylike
isn't true often enough for the perf difference to matter.
If you still prefer it otherwise, I'll switch it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel strongly either. I don't have any other objections, but I'll wait with merging this until it's clear what Jukka wanted. Or Jukka can merge.
class C(Any): | ||
pass | ||
x = None # type: Optional[C] | ||
x.foo() # E: Some element of union has no attribute "foo" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideas for additional test cases:
- Test case for using
isinstance
with something likeOptional[C]
(C
has anAny
base class), and test how the conditional type binder works in that case. - Similar to above (
isinstance
), but withUnion[Any, int]
where there is noAny
base class. - Test accessing an attribute of
Union[Any, int]
where there is noAny
base class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something, but those test cases seem completely unrelated to this issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces a new sort of union type (one with both Any
and non-Any
types), and some other code might make assumptions about this and not work correctly with them. I haven't looked at the relevant code, but it's probably as easy to write the tests than to manually validate that everything is okay. In particular, I remember than union-related operations were implemented in a slightly sketchy fashion and thus might have problems (though they are probably fine)... Alternatively, it would be equally fine to have unit tests that just verify that operations on unions still do the right thing -- restrict_subtype_away
comes to mind, in particular.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually doesn't introduce a new Union type. Explicitly writing Union[A, B]
called make_union
, not make_simplified_union
, so these types already exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I remember seeing that at some point. Thus if things are broken, they were so already. I'll probably file a new issue -- some union-related code I browsed looked pretty suspicious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was already an issue open for one of the bugs I was worried about: #1720
@JukkaL is there anything more you'd like to see here before merging? |
About my comment: Using a real subtype check would certainly be a better fix than what I suggested. However, the current PR seems incomplete in that it special cases The real subtype check might be somewhat non-trivial to implement in a way that handles all the corner cases correctly, so I think that it would be reasonable to make a temporary fix that is still not quite correct but less incorrect than what we currently have, as the required incremental change would be small :-) About subtypes vs compatibility: I don't see how Historically, I used 'is subtype of' to mean what is currently understood as 'is compatible with', and 'is proper subtype of' for what is currently understood as 'is subtype of'. In retrospect that was a bad decision, and I can well see how it's confusing to use 'subtype' to mean something that is quite different from the textbook definition. |
if i != j and is_subtype(tj, ti): | ||
# Attempt to only combine true subtypes by avoiding types containing Any. | ||
# TODO: Properly exclude generics and functions. | ||
tj_is_anylike = (isinstance(tj, AnyType) or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if there are multiple Any
types in the union to simplify? I think that we should remove duplicates away at least, perhaps by using is_same_type
. So Union[Any, Any, int]
would be simplified into Union[Any, int]
.
Edited
As we are pretty close to release, I'm fine with postponing this until the next release. Then we could perhaps release a larger set of fixes to unions. But if @ddfisher wants to get this in it doesn't look very risky once the duplicate |
OK let's postpone it. (David agreed off-line.)
|
@ddfisher Can you look at simplifying away duplicate |
Ping |
To do this right, I'm going to fix up |
BTW, can someone explain what "proper subtype" is supposed to mean in the context of mypy? I never understood it. |
Here's my understanding: in mypy, "subtype" means "compatible with" and "proper subtype" means "subtype". |
Okay, that makes sense. I saw the terminology "compatible with" elsewhere in mypy and thought it was a separate, third concept but I guess it isn't. |
I need something similar for another thing I'm working on, so I'm probably going to do another PR that replaces this. I'll keep this open until I have the other PR ready. |
(This is an updated version of #2197 by ddfisher.) make_simplified_union had two incorrect interactions with Any that this fixes: 1) Any unions containing Any were simplified to Any, which is incorrect. 2) Any classes that inherited from Any would be removed from the union by the subclass simplification (because subclasses of Any are considered subclasses of all other classes due to subtype/compatible with confusion). Note that `Union`s call make_union and not make_simplified_union, so this doesn't change their behavior directly (unless they interact with something else which causes them to be simplified, like being part of an `Optional`).
#2714 is an updated version of this PR -- it handles some additional cases and adds tests. |
make_simplified_union had two incorrect interactions with Any that this
fixes:
by the subclass simplification (because subclasses of Any are
considered subclasses of all other classes due to subtype/compatible
with confusion).
Note that
Union
s call make_union and not make_simplified_union, sothis doesn't change their behavior directly (unless they interact with
something else which causes them to be simplified, like being part of
an
Optional
).